/**
 * Copyright (c) 2008, Song Hui, Peking University, All rights
 * reserved.
 */
package cn.edu.pku.sei.ra.framework;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;


import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.Path;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EAnnotation;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EPackage;
import org.eclipse.emf.ecore.EReference;
import org.eclipse.emf.ecore.EcorePackage;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.util.EcoreUtil;

import cn.edu.pku.sei.ra.util.ECoreRelatedUtil;

import ac.jp.u_tokyo.QVTWrapper.QVTTransformation;
import ac.jp.u_tokyo.QVTWrapper.SynchronizationFailedException;
import ac.jp.u_tokyo.QVTWrapper.ToBeanbagMerger;

/**
 *
 */
public class CausalConnector {
	
	public static final String RSA_ANNOTATION="http://www.sei.pku.edu.cn/rsa";
	
	//need to set before running
	private String arcMetaURIStr=null;
	private String sysMetaURIStr=null;
	private URI arcMetaURI=null;
	private URI sysMetaURI=null;
	private EPackage sysWrappingPackage=null;
	private String qvtPath=null;
	private String arcPath=null;
	private String utilDir=null;
	
	private String transName=null;
	private String arcName=null;
	private String sysName=null;
	
	
	//do not need to set
	private ResourceEnv resEnv=null;
	private EPackage arcPackage=null;
	private EPackage sysPackage=null;
	
	private String oriPath;
	private Reader qvtReader;
	
	public CausalConnector() {
		// TODO Auto-generated constructor stub
	}
	
	private Resource getOriginArc(Resource arc){
		int i=arcPath.lastIndexOf(".");
		oriPath = arcPath.substring(0,i)+"_origin"+arcPath.substring(i);
		i=oriPath.lastIndexOf("/");
		if(i==-1)
			i=oriPath.lastIndexOf("\\");
		oriPath = utilDir+oriPath.substring(i+1);
		URI oriURI=URI.createFileURI(oriPath);
/*		if(!ResourcesPlugin.getWorkspace().getRoot()
				.getFile(new Path(oriPath)).exists()){
			Resource res=resEnv.getResourceSet().createResource(oriURI);
			res.getContents().addAll(EcoreUtil.copyAll(arc.getContents()));
			return res;
		}*/
		try{
			return resEnv.getResourceSet().getResource(oriURI, true);
		}
		catch(Exception e){
			Resource res=resEnv.getResourceSet().createResource(oriURI);
			res.getContents().addAll(EcoreUtil.copyAll(arc.getContents()));
			return res;
		}
	}
	
	private QVTTransformation getTransformation(EPackage srcMeta, EPackage tgtMeta) 
		throws IOException
	{
		ArrayList<EPackage> metas=new ArrayList<EPackage>();
		metas.add(srcMeta);
		metas.add(tgtMeta);
		
		qvtReader = new BufferedReader(
					new FileReader(qvtPath)
				);
		qvtReader.mark(0);
		return new QVTTransformation(
				metas,
				System.out,
				qvtReader,
				utilDir,
				transName,
				arcName,
				sysName
		);

	}
	
	
	private Resource getDynamicSysState(){
		EPackage staticSysPackage=resEnv.getResourceSet().getPackageRegistry().getEPackage(sysMetaURIStr);
		EPackage dynamicSysPackage=sysWrappingPackage;
		resEnv.registerPackage(sysMetaURIStr, dynamicSysPackage);
		Resource dynamicSysState=resEnv.createInterResource();
		
		EReference ownedClass=EcorePackage.eINSTANCE.getEPackage_EClassifiers();
		EList allClass=(EList)dynamicSysPackage.eGet(ownedClass);
		EClass rootClass=null;
		for(Object ob : allClass){
			if(!(ob instanceof EClass)){
				continue;
			}
			EClass cls=(EClass)ob;
			EAnnotation ant=cls.getEAnnotation(CausalConnector.RSA_ANNOTATION);
			if(ant==null)
				continue;
			if("true".equals(ant.getDetails().get("RootClass"))){
				rootClass=cls;
				break;
			}
		}
		if(rootClass==null)
			throw new RARuntimeException("No Root Class Specified in System MetaModel");
		EObject root=dynamicSysPackage.getEFactoryInstance().create(rootClass);
		dynamicSysState.getContents().add(root);
		return dynamicSysState;
	}
	/**
	 * @throws IOException 
	 * @throws SynchronizationFailedException 
	 * 
	 */
	public void maintain() throws IOException, SynchronizationFailedException{
		
		long start=System.currentTimeMillis();
		//Static resources
		Resource arcOri, arcMod, sysOri, sysCurr, sysTransed, sysMerged, arcTransed, arcMerged;
		
		//Dynamic resource
		Resource dynamicSys;
		
		Resource arcModToUnload=null;
		Resource arcOriToUnload=null;
		
		resEnv=new ResourceEnv();
		arcPackage=resEnv.getEPackage(arcMetaURI);
		sysPackage=resEnv.getEPackage(sysMetaURI);
		
		resEnv.registerPackage(arcMetaURIStr, arcPackage);
		
		/*-------NOTICE: Registered Static Package-------------*/
		
		resEnv.registerPackage(sysMetaURIStr, sysPackage);
		
		arcMod=resEnv.getResourceSet().getResource(
				URI.createFileURI(arcPath),
				true
			);
		arcOri=getOriginArc(arcMod);
		
		System.out.println("After load:"+String.valueOf(System.currentTimeMillis()-start));
		
		arcModToUnload=arcMod;
		arcOriToUnload=arcOri;
		
		sysOri=resEnv.createInterResource();
		sysTransed=resEnv.createInterResource();
		
		System.out.println("before copy:"+String.valueOf(System.currentTimeMillis()-start));
		
		Resource copyArcMod=resEnv.createInterResource();
		copyArcMod.getContents().addAll(EcoreUtil.copyAll(arcMod.getContents()));
		Resource copyArcOri=resEnv.createInterResource();
		copyArcOri.getContents().addAll(EcoreUtil.copyAll(arcOri.getContents()));
		
		System.out.println("befor forward trans:"+String.valueOf(System.currentTimeMillis()-start));
		
		sysOri = getTransformation(arcPackage,sysPackage)
					.forward(copyArcOri, sysOri);
		Resource copySysOri=resEnv.createInterResource();
		copySysOri.getContents().addAll(EcoreUtil.copyAll(sysOri.getContents()));
		sysOri=copySysOri;
		
		
		
		sysTransed=getTransformation(arcPackage,sysPackage)
					.forward(copyArcMod, sysTransed);
		
		
		Resource tmpArcEmptyForOld=resEnv.createInterResource();		
		Resource tmpArcSysOld=getTransformation(arcPackage,sysPackage)
								.backward(tmpArcEmptyForOld, sysTransed);
		/*------------DEBUG-------------*/
		//arcOri.save(System.out, Collections.EMPTY_MAP);
		//arcMod.save(System.out, Collections.EMPTY_MAP);
		//sysOri.save(System.out,Collections.EMPTY_MAP);
		//sysTransed.save(System.out,Collections.EMPTY_MAP);
		/*------------DEBUG-------------*/

		
		System.out.println("After forward trans:"+String.valueOf(System.currentTimeMillis()-start));
		
		
		sysCurr = getStaticCurrentState();

		System.out.println("After first retrieve:"+String.valueOf(System.currentTimeMillis()-start));
		
		
		/*---------debug-----------*/
//		sysOri.save(System.out, Collections.EMPTY_MAP);
//		sysCurr.save(System.out, Collections.EMPTY_MAP);
//		sysTransed.save(System.out, Collections.EMPTY_MAP);
		/*-----------------------*/
		
		sysMerged=new ToBeanbagMerger().merge(sysOri, sysCurr, sysTransed);
		
		System.out.println("After merge"+String.valueOf(System.currentTimeMillis()-start));
		
		
		/*-----------debug------*/
//		sysCurr.save(System.out, Collections.EMPTY_MAP);
//		sysMerged.save(System.out, Collections.EMPTY_MAP);
		/*----------------------*/
		
		ModelDiffer differForAdd=new ModelDiffer(resEnv);
		Resource toAdd=differForAdd.diffResource(sysMerged, sysCurr);
		ModelDiffer differForRemove=new ModelDiffer(resEnv);
		Resource toRemove=differForRemove.diffResource(sysCurr, sysMerged);

		System.out.println("After differ:"+String.valueOf(System.currentTimeMillis()-start));
		
		/*-----------debug------*/
		System.out.println("----------To Add------------*");
		toAdd.save(System.out, Collections.EMPTY_MAP);
		System.out.println("----------To Remove------------*");
		toRemove.save(System.out, Collections.EMPTY_MAP);
		/*----------------------*/
		
		System.out.println("After print:"+String.valueOf(System.currentTimeMillis()-start));
		
		/*------------NOTICE: Registered Dynamic Package again----*/
		resEnv.registerPackage(sysMetaURIStr,sysWrappingPackage);

		dynamicSys=getDynamicSysState();
		differForAdd.add(dynamicSys,sysWrappingPackage);
		differForRemove.remove(dynamicSys,sysWrappingPackage);
		
		System.out.println("After add and remove:"+String.valueOf(System.currentTimeMillis()-start));
		
		/*retrieve the actual modification effect*/
		sysCurr=getStaticCurrentState();
		/*------NOTICE: The above method HAVE Registered STATIC Package again-----*/
		
		Resource tmpArcEmpty=resEnv.createInterResource();
		Resource tmpArcSysCurr=getTransformation(arcPackage,sysPackage)
								.backward(tmpArcEmpty, sysCurr);

			
		/*--debug*---*/
//		System.out.println("----------before transform------------*");
//		arcMod.save(System.out, Collections.EMPTY_MAP);
		/*==debug*---*/
		
//		Resource arcEmpty=resEnv.createInterResource();
		
		arcTransed=getTransformation(arcPackage,sysPackage)
					.backward(arcMod, sysCurr);
		System.out.println("After many backs:"+String.valueOf(System.currentTimeMillis()-start));

		/*-----------debug------*/
//		sysCurr.save(System.out,Collections.EMPTY_MAP);
//		System.out.println("----------Transed Arch------------*");
//		arcTransed.save(System.out, Collections.EMPTY_MAP);
//		System.out.println("----------after transform------------*");
//		arcMod.save(System.out, Collections.EMPTY_MAP);
		/*----------------------*/
		
		reviseArcTransed(arcTransed,tmpArcSysOld,tmpArcSysCurr);

		
		/*-----------debug------*/
//		sysCurr.save(System.out,Collections.EMPTY_MAP);
//		System.out.println("----------Original Arch------------*");
//		arcOri.save(System.out, Collections.EMPTY_MAP);
//		System.out.println("----------Transed Arch------------*");
//		arcTransed.save(System.out, Collections.EMPTY_MAP);
//		System.out.println("----------Mod Arch------------*");
//		arcMod.save(System.out, Collections.EMPTY_MAP);
		/*----------------------*/
		
		/**
		 * Two alternative solutions: merge and warning or just use the
		 * transformation result
		 * TODO: Need further consideration
		 */
		//arcMerged=new ToBeanbagMerger().merge(arcOri, arcMod, arcTransed);
		arcMerged=arcTransed;
		
		
		arcMerged.setURI(arcMod.getURI());
		arcMerged.save(Collections.EMPTY_MAP);	
		arcMerged.setURI(arcOri.getURI());
		arcMerged.save(Collections.EMPTY_MAP);

		/*pretending nothing has happened*/
		/*remove to the outside*/
		arcModToUnload.unload();
		arcOriToUnload.unload();
		resEnv.revertGloble();		
	}
	
	public void revertGloblePackageRegister(){
		resEnv.revertGloble();
	}

	private Resource getStaticCurrentState() throws IOException {
		Resource sysCurr;
		Resource dynamicSys;
		/*--------NOTICE: Registered DYNAMIC Package-----------*/
		resEnv.registerPackage(sysMetaURIStr, sysWrappingPackage);
		dynamicSys=getDynamicSysState();
		
		ECoreRelatedUtil.refreshEObject(dynamicSys.getContents().iterator().next());
		ByteArrayOutputStream out=new ByteArrayOutputStream();
		dynamicSys.save(out,Collections.EMPTY_MAP);
		
		/*--------NOTICE: Registered STATIC Package again-----*/
		resEnv.registerPackage(sysMetaURIStr, sysPackage);
		sysCurr=resEnv.createInterResource();
		ByteArrayInputStream in=new ByteArrayInputStream(out.toByteArray());
		sysCurr.load(in,Collections.EMPTY_MAP);
		return sysCurr;
	}


	public void setArcMetaURIStr(String arcMetaURIStr) {
		this.arcMetaURIStr = arcMetaURIStr;
	}

	public String getArcMetaURIStr() {
		return arcMetaURIStr;
	}

	public void setSysMetaURIStr(String sysMetaURIStr) {
		this.sysMetaURIStr = sysMetaURIStr;
	}

	public String getSysMetaURIStr() {
		return sysMetaURIStr;
	}

	

	public void setSysWrappingPackage(EPackage sysWrappingPackage) {
		this.sysWrappingPackage = sysWrappingPackage;
	}

	public EPackage getSysWrappingPackage() {
		return sysWrappingPackage;
	}

	public void setQvtPath(String qvtPath) {
		this.qvtPath = qvtPath;
	}

	public String getQvtPath() {
		return qvtPath;
	}

	public void setArcPath(String arcPath) {
		this.arcPath = arcPath;
	}

	public String getArcPath() {
		return arcPath;
	}

	public void setUtilDir(String utilDir) {
		this.utilDir = utilDir;
	}

	public String getUtilDir() {
		return utilDir;
	}
	public void setArcMetaURI(URI arcMetaURI) {
		this.arcMetaURI = arcMetaURI;
	}
	public URI getArcMetaURI() {
		return arcMetaURI;
	}
	public void setSysMetaURI(URI sysMetaURI) {
		this.sysMetaURI = sysMetaURI;
	}
	public URI getSysMetaURI() {
		return sysMetaURI;
	}

	public void setTransName(String transName) {
		this.transName = transName;
	}

	public String getTransName() {
		return transName;
	}

	public void setArcName(String arcName) {
		this.arcName = arcName;
	}

	public String getSysName() {
		return sysName;
	}
	
	public void setSysName(String sysName) {
		this.sysName = sysName;
	}

	public String getArcName() {
		return arcName;
	}

	public void setSysPackage(EPackage sysPackage) {
		this.sysPackage = sysPackage;
	}

	public EPackage getSysPackage() {
		return sysPackage;
	}
	
	public void forTest() throws IOException{
		resEnv=new ResourceEnv();
		arcPackage=resEnv.getEPackage(arcMetaURI);
		sysPackage=resEnv.getEPackage(sysMetaURI);
		
		resEnv.registerPackage(arcMetaURIStr, arcPackage);
		
		/*-------NOTICE: Registered Static Package-------------*/
		
		resEnv.registerPackage(sysMetaURIStr, sysPackage);

		Resource tgt=getStaticCurrentState();
		Resource src=resEnv.createInterResource();
		Resource res=getTransformation(arcPackage,sysPackage).backward(src, tgt);
		res.save(System.out,Collections.EMPTY_MAP);
	}
	
	public String toString(){
		try{
			return String.format(
					"%s(%s)<=%s=>%s",
					extractFileName(arcMetaURIStr),
					extractFileName(arcPath),
					extractFileName(qvtPath),
					extractFileName(sysMetaURIStr)
			);
		}
		catch(Exception e){
			e.printStackTrace();
			return "Not a valid Causal Connector";
		}
	}

	private String extractFileName(String path){
		int i=path.lastIndexOf("/");
		return(path.substring(i+1));
	}
	
	public boolean silentMaintain(){
		try{
			maintain();
			return true;
		}
		catch(Exception e){
			e.printStackTrace();
			return false;
		}
	}
	
	private void reviseArcTransed(Resource arcTransed, Resource arcSysOld, Resource arcSysCurr){
		List<EObject> toDelete=new ArrayList<EObject>();
		for(Iterator<EObject> it=arcTransed.getAllContents(); it.hasNext();){
			EObject transed=it.next();
			if(contain(arcSysOld.getAllContents(),transed) 
					&& !contain(arcSysCurr.getAllContents(),transed))
			{
				toDelete.add(transed);
			}
		}
		for(EObject obj:toDelete){
			EcoreUtil.delete(obj);
		}
	}
	
	private boolean contain(Iterator<EObject> it, EObject obj){
		while(it.hasNext())
			if(ECoreRelatedUtil.checkTheSameForDiffTypes(it.next(), obj))
				return true;
		return false;
	}
	
}
